package top.jacktgq.hsp.nio.groupchat;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * @Author CandyWall
 * @Date 2020/11/11--18:34
 * @Description NIO群聊的服务器
 */
public class GroupChatServer {
    //多路复用器（选择器）
    private Selector selector;

    //监听其他端口的Channel
    private ServerSocketChannel listenerChannel;

    //聊天服务器监听的端口号
    private static final int PORT = 6666;

    //构造器
    //初始化工作
    public GroupChatServer() {
        try {
            //得到选择器
            selector = Selector.open();

            //获取ServerSocketChannel
            listenerChannel = ServerSocketChannel.open();

            //绑定端口
            listenerChannel.socket().bind(new InetSocketAddress(PORT));

            //设置非阻塞模式
            listenerChannel.configureBlocking(false);

            //将该listenerChannel在selector中注册，并设置关心OP_ACCEPT事件
            listenerChannel.register(selector, SelectionKey.OP_ACCEPT);

            listen();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //监听
    public void listen() {
        System.out.println("群聊服务器端启动成功...");
        try {
            //循环处理
            while (true) {
                if (selector.select(/*2000*/) > 0) {
                    //遍历得到SelectionKey集合
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
                    while (keyIterator.hasNext()) {
                        //取出SelectionKey
                        SelectionKey selectionKey = keyIterator.next();
                        //监听到的是OP_ACCEPT事件
                        if (selectionKey.isAcceptable()) {
                            //服务器建立连接，创建对应的SocketChannel
                            SocketChannel socketChannel = listenerChannel.accept();
                            //设置为非阻塞模式
                            socketChannel.configureBlocking(false);
                            //将socketChannel在selector中注册，并关注OP_READ
                            socketChannel.register(selector, SelectionKey.OP_READ);

                            //提示xxx上线了
                            System.out.println(socketChannel.getRemoteAddress().toString().substring(1) + " 上线");
                            //将当前用户上线的消息发送给其他客户端
                            sendMsgToOthers(socketChannel,socketChannel.getRemoteAddress().toString().substring(1) + " 上线");
                        }
                        //监听到的是OP_READ事件，即通道是可读的状态
                        if (selectionKey.isReadable()) {
                            //处理读
                            readData(selectionKey);
                        }
                    }

                    //把当前的key删除，防止重复处理
                    //注：只是从selectedKeys集合中移除了，并没有从Selector中移除，
                    //selector不会自己删除selectedKeys()集合中的selectionKey，
                    //那么如果不人工remove()，将导致下次select()的时候selectedKeys()中
                    //仍有上次轮询留下来的信息，这样必然会出现错误，假设这次轮询时该通道
                    //并没有准备好，却又由于上次轮询未被remove()的原因被认为已经准备好了，这样能不出错吗？
                    keyIterator.remove();
                } /*else {
                    System.out.println("已经等待了2秒，没有客户端连接...");
                }*/
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {

        }
    }

    //读取客户端消息
    private void readData(SelectionKey key) {
        //根据SelectionKey获取关联的SocketChannel
        SocketChannel socketChannel = (SocketChannel) key.channel();
        //获取客户端发的消息可以从SelectionKey的Attachment中拿，或者从socketChannel中拿

        String msg = null;
        try {
            //方法1：从当前的socketChannel中获取Channel值
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            int count = socketChannel.read(byteBuffer);
            //根据count值做处理
            if (count > 0) {
                msg = socketChannel.getRemoteAddress().toString().substring(1) + " 说：" + new String(byteBuffer.array(), "UTF-8");
                System.out.println("消息转发-----" + msg);
                //将消息发送给其他客户端（去掉自己）
                sendMsgToOthers(socketChannel, msg);
            }

            //方法2根据SelectionKey获取SocketChannel关联的ByteBuffer
            /*ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
            msg = socketChannel.getRemoteAddress() + " 说：" + new String(byteBuffer.array(), "UTF-8");
            sendMsgToOthers(socketChannel, msg);*/

        } catch (IOException e) {
            //e.printStackTrace();
            try {
                System.out.println(socketChannel.getRemoteAddress().toString().substring(1) + " 离线了...");
                //将当前用户离线的消息转发给其他客户端
                sendMsgToOthers(socketChannel, socketChannel.getRemoteAddress().toString().substring(1) + " 离线了...");
                //取消注册，从Selector中移除当前key
                key.cancel();
                //关闭通道
                socketChannel.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }


    }

    //将消息转发给其他客户端（去掉自己）
    private void sendMsgToOthers(SocketChannel fromChannel, String msg) {
        Iterator<SelectionKey> keyIterator = selector.keys().iterator();
        while (keyIterator.hasNext()) {
            SelectionKey selectionKey = keyIterator.next();
            //如果是自己，就不用发了
            //这里不能进行强转，因为ServerSocketChannel也在Selector里面注册了，
            //所以遍历到ServerSocketChannel然后转成SocketChannel会报类型转换错误
            //SocketChannel channel = (SocketChannel) selectionKey.channel();
            SelectableChannel channel = selectionKey.channel();
            if(channel instanceof SocketChannel && !channel.equals(fromChannel)) {
                //只有确认不是自己并且是SocketChannel类型才能进行强转
                SocketChannel socketChannel = (SocketChannel) channel;
                try {
                    socketChannel.write(ByteBuffer.wrap(msg.getBytes()));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    public static void main(String[] args) {
        new GroupChatServer();
    }

}
